package com.mayfool.task.aop;

import jakarta.persistence.Index;
import jakarta.persistence.Table;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Example;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Optional;

/**
 * @author zhou.xu
 * @since 2024/9/1 11:01
 */
@Slf4j
@Aspect
@Component
public class UniqueSaveAspect {

    private static final Method findOneMethod = ReflectionUtils.findMethod(JpaRepository.class, "findOne", Example.class);

    @Around("(execution(public * com.mayfool.task..dao..*.save(..)))")
    public Object doAroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        // 根据唯一性约束条件进行save操作
        // 1、获取对象唯一性键
        Object[] args = pjp.getArgs();
        if (args.length == 1) {
            Object arg = args[0];
            // 2、根据唯一性键进行查询
            Object uniqueParam = getUniqueParam(arg);
            if (uniqueParam == null) {
                return pjp.proceed();
            }
            // 3、如果查询到，则返回查询到的对象
            if (findOneMethod != null) {
                Object target = pjp.getTarget();
                Object exists = AopUtils.invokeJoinpointUsingReflection(target, findOneMethod, new Object[]{Example.of(uniqueParam)});
                if (exists instanceof Optional && ((Optional<?>) exists).isPresent()) {
                    return ((Optional<?>) exists).get();
                }
            }
        }
        // 4、如果查询不到，则执行save操作
        return pjp.proceed();
    }

    public static Object getUniqueParam(Object arg) {
        Class<?> aClass = arg.getClass();
        Table annotation = aClass.getAnnotation(Table.class);
        Index[] indexes = annotation.indexes();
        Index index = Arrays.stream(indexes).filter(Index::unique).findFirst().orElse(null);
        if (index == null) {
            return null;
        }
        String[] columns = index.columnList().split(",");
        if (columns.length == 0) {
            return null;
        }
        Object param = BeanUtils.instantiateClass(aClass);
        for (String column : columns) {
            Field field = ReflectionUtils.findField(aClass, column);
            if (field != null) {
                field.setAccessible(true);
                ReflectionUtils.setField(field, param, ReflectionUtils.getField(field, arg));
            }
        }
        return param;
    }
}
